﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace regx
{
    class Fusker
    {
        private static Regex _rxSplit = new Regex(@"(?<pre>^[^\[]*)(?:\[(?<range>[^]]*)\])?(?<post>.*$)", RegexOptions.ExplicitCapture);

        /// <summary>
        /// Expands range and replacements in a fusker-like string
        /// </summary>
        /// <param name="src">A string with any number of fusker range parts</param>
        /// <returns>All expanded strings generated by the fusk expression</returns>
        public static IEnumerable<string> Expand(string src)
        {   //=================================================================
            if (src == null)         {yield break;}
            if (src == string.Empty) {yield return string.Empty;        yield break;}

            Match match = _rxSplit.Match(src);
            
            // If no range was found, return the string;
            if (!match.Groups["range"].Success) {yield return src;      yield break;}

            string pre  = match.Groups["pre"].Success  ? match.Groups["pre"].Value  : string.Empty;
            string post = match.Groups["post"].Success ? match.Groups["post"].Value : string.Empty;

            // Expand the first range block found
            foreach (string val in ExpandRangeBlock(match.Groups["range"].Value))
            {
                // And cross product that with calling this function on everything after the range block
                foreach (string postval in Expand(post))
                {
                    yield return pre + val + postval;
                }
            }
        }

        protected static IEnumerable<string> ExpandRangeBlock(string rangeblock)
        {   //=================================================================
            // Start by splitting on commas
            string[] acommas = rangeblock.Split(',');
            foreach (string rangepart in acommas)
            {
                foreach (string rangepartexpand in ExpandRange(rangepart)) yield return rangepartexpand;
            }
        }

        protected static bool IsNumericRange(string start, string end)
        {   //=================================================================
            uint n1 = 0;
            uint n2 = 0;
            return (uint.TryParse(start, out n1) && uint.TryParse(end, out n2) && (n1 <= n2));
        }

        protected static IEnumerable<string> ExpandNumericRange(string start, string end)
        {   //=================================================================
            bool PadWithZeroes = ('0' == start[0]);

            for (uint i = uint.Parse(start); i <= uint.Parse(end); i += 1)
            {
                string ret = i.ToString();
                if (PadWithZeroes) {while (ret.Length < start.Length) ret = "0" + ret;}
                yield return ret;
            }

        }

        protected static bool IsCharacterRange(string start, string end)
        {   //=================================================================
            return start.Length == 1         // One character
                && end.Length   == 1         // One character
                && Char.IsLetter(start[0])   // Is a letter
                && Char.IsLetter(end[0])     // Is a letter
                && (Char.IsLower(start[0]) == Char.IsLower(end[0]))    // Same case
                && start[0] <= end[0];       // Right order
        }

        protected static IEnumerable<string> ExpandCharacterRange(string start, string end)
        {   //=================================================================
            for (char c = start[0]; c <= end[0]; c++)
            {
                yield return c.ToString();
            }
        }

        protected enum RangeType {Unknown, NoRange, Number, Character}
        protected static RangeType GetRangeType(string[] RangeParts)
        {   //=================================================================
            switch (RangeParts.Count())
            {
                default: return RangeType.Unknown;
                case 1:  return RangeType.NoRange;
                case 2:  return IsNumericRange(RangeParts[0],   RangeParts[1]) ? RangeType.Number :
                                IsCharacterRange(RangeParts[0], RangeParts[1]) ? RangeType.Character :
                                                                                 RangeType.Unknown;
            }

        }

        protected static IEnumerable<string> ExpandRange(string range)
        {   //=================================================================
            string[] RangeParts = range.Split('-');
            switch (GetRangeType(RangeParts))
            {
                default: break;
                case RangeType.NoRange:   yield return range; break;
                case RangeType.Number:    foreach (string s in ExpandNumericRange(RangeParts[0],   RangeParts[1])) yield return s; break;
                case RangeType.Character: foreach (string s in ExpandCharacterRange(RangeParts[0], RangeParts[1])) yield return s; break;
            }
        }
    }
}
